package com.tools.web.wx;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import com.tools.common.exception.DataGenerationException;
import com.tools.common.exception.NetRequestRuntimeException;
import com.tools.common.exception.NotFoundDataException;
import com.tools.common.io.CharsetEnum;
import com.tools.common.object.Note;
import com.tools.web.http.HttpBuilder;
import com.tools.web.http.HttpResult;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;

import java.net.URLEncoder;
import java.util.Map;
import java.util.Objects;

@Note("微信 API 相关操作的工具类")
public final class WxKit {

    private WxKit() {}

    /* ************************************************************************************************************
     *
     *      新建服务接口 interface
     *
     * ************************************************************************************************************
     * */

    @Note("【小程序服务】：新建 WxMaService 接口的实现类对象")
    public static WxMaServiceImpl newWxMaServiceImpl(WxSource source) {
        if(source == null) throw new NullPointerException("微信 API 数据源对象为 null");
        if(source.allNotEmpty()) {
            WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
            config.setAppid(source.getAppId());
            config.setSecret(source.getSecret());
            WxMaServiceImpl wxMaServiceImpl = new WxMaServiceImpl();
            wxMaServiceImpl.setWxMaConfig(config);
            return wxMaServiceImpl;
        }
        throw new IllegalArgumentException("微信 API 所需的 appid 和密钥为空");
    }

    @Note("【公众号服务】：新建 WxMpService 接口的实现类对象")
    public static WxMpServiceImpl newWxMpServiceImpl(WxSource source) {
        if(source == null) throw new NullPointerException("微信 API 数据源对象为 null");
        if(source.allNotEmpty()) {
            WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
            config.setAppId(source.getAppId());
            config.setSecret(source.getSecret());
            WxMpServiceImpl wxMpService = new WxMpServiceImpl();
            wxMpService.setWxMpConfigStorage(config);
            return wxMpService;
        }
        throw new IllegalArgumentException("微信 API 所需的 appid 和密钥为空");
    }


    /* ************************************************************************************************************
     *
     *      小程序相关操作
     *
     * ************************************************************************************************************
     * */

    @Note("【用户小程序】：根据前端传过来的 code 登录小程序，获取对应的 openid 和 sessionKey 等数据")
    public static WxMaJscode2SessionResult miniAppLogin(WxMaService wxMaService, String code) {
        try {
            return wxMaService.jsCode2SessionInfo(code);
        } catch (Exception e) {
            throw new NetRequestRuntimeException(e);
        }
    }


    @Note("【用户小程序】：根据前端传过来的 encryptData 和 iv，配合 sessionKey 解密数据，获取用户信息")
    public static WxMaUserInfo getWxMiniAppUserInfo(WxMaService wxMaService, String sessionKey,
                                             String encryptData, String iv) {
        return wxMaService.getUserService().getUserInfo(sessionKey, encryptData, iv);
    }


    @Note("【用户小程序】：根据前端传过来的 encryptData 获取用户的手机号码")
    public static WxMaPhoneNumberInfo getWxPhoneNumberInfo(WxMaService wxMaService, String encryptData) {
        try {
            return wxMaService.getUserService().getPhoneNoInfo(encryptData);
        } catch (Exception e) {
            throw new NetRequestRuntimeException(e);
        }
    }

    /* ************************************************************************************************************
     *
     *      公众号的相关操作
     *
     * ************************************************************************************************************
     * */

    @Note("新建用于跳转/扫码的登录 URL")
    public static String newQRLoginURL(String appId, String state, String redirectURL) {
        return newQRLoginURL(appId, state, redirectURL, CharsetEnum.UTF_8);
    }

    @Note("新建用于跳转/扫码的登录 URL")
    public static String newQRLoginURL(String appId, String state, String redirectURL, CharsetEnum charset) {
        try {
            String encodingURL = URLEncoder.encode(redirectURL, charset.getValue());
            return "https://open.weixin.qq.com/connect/qrconnect?&response_type=code&scope=snsapi_login&state=" +
                    state +
                    "&appid=" + appId +
                    "&redirect_uri=" + encodingURL +
                    "#wechat_redirect";
        } catch (Exception e) {
            throw new DataGenerationException(e);
        }
    }

    @Note("请求微信服务器，获取 access_token 和 openId")
    public static WxLogin getWxLoginAccessToken(String code, String appId, String secret) {
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code&appid="
                + appId
                + "&secret=" + secret
                + "&code=" + code;
        HttpResult httpResult = HttpBuilder.of(url).printLog(true).executeGet();
        Map<String, Object> contentMap = httpResult.getContentMap();
        if(contentMap == null) throw new NotFoundDataException("微信公众号登录接口返回的 JSON 字符串转为 Map 集合为 null");
        if(contentMap.containsKey("errcode")) {
            String errorMessage = "错误状态码: " + contentMap.get("errcode")
                    + ", 错误信息: " + contentMap.get("errmsg");
            throw new NetRequestRuntimeException(errorMessage);
        }
        String accessToken = (String) contentMap.get("access_token");
        String openId = (String) contentMap.get("openid");
        return new WxLogin(accessToken, openId);
    }


    @Note("根据公众号提供的 accessToken 和 openId 获取微信用户的信息")
    public static Map<String, Object> getWxUserInfo(String accessToken, String openId) {
        String url = "https://api.weixin.qq.com/sns/userinfo?" +
                "access_token=" + accessToken +
                "&openid=" + openId;
        HttpResult httpResult = HttpBuilder.of(url).printLog(true).executeGet();
        Map<String, Object> contentMap = httpResult.getContentMap();
        contentMap.forEach((k, v) -> {
            System.out.println(k + " ==> " + v);
        });
        return contentMap;
    }


    @Note("网页版登录，然后获取用户信息返回")
    public static Map<String, Object> loginForWeb(String code, String appId, String secret) {
        WxLogin wxLogin = getWxLoginAccessToken(code, appId, secret);
        return getWxUserInfo(wxLogin.getAccessToken(), wxLogin.getOpenId());
    }


    /**
     * 微信登录信息包装内部类
     * */
    @Note("微信登录信息包装内部类")
    public static class WxLogin {
        private final String accessToken;
        private final String openId;

        private WxLogin(String accessToken, String openId) {
            this.accessToken = accessToken;
            this.openId = openId;
        }

        public String getOpenId() {
            return openId;
        }

        public String getAccessToken() {
            return accessToken;
        }

        @Override
        public String toString() {
            return "WxLogin{" +
                    "accessToken=" + accessToken +
                    ", openId=" + openId +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            WxLogin wxLogin = (WxLogin) o;
            return Objects.equals(accessToken, wxLogin.accessToken) && Objects.equals(openId, wxLogin.openId);
        }

        @Override
        public int hashCode() {
            return Objects.hash(accessToken, openId);
        }
    }
}
